#pragma once

//========================================================================
//
//  Original Code written at Compulsive Development
//
//========================================================================


//--------------------------------------------------------------------------------
// Includes
//--------------------------------------------------------------------------------

#include <functional>
#include <vector>
#include "Vector.h"
#include "Matrix.h"
#include "Quaternion.h"
#include "Plane.h"

//--------------------------------------------------------------------------------
// Defines
//--------------------------------------------------------------------------------

const float  PI = 3.14159265358979f;
const float  PI2 = 2 * PI;

//--------------------------------------------------------------------------------

/* Period parameters */  
#define CMATH_N 624
#define CMATH_M 397
#define CMATH_MATRIX_A 0x9908b0df   /* constant vector a */
#define CMATH_UPPER_MASK 0x80000000 /* most significant w-r bits */
#define CMATH_LOWER_MASK 0x7fffffff /* least significant r bits */

/* Tempering parameters */   
#define CMATH_TEMPERING_MASK_B 0x9d2c5680
#define CMATH_TEMPERING_MASK_C 0xefc60000
#define CMATH_TEMPERING_SHIFT_U(y)  (y >> 11)
#define CMATH_TEMPERING_SHIFT_S(y)  (y << 7)
#define CMATH_TEMPERING_SHIFT_T(y)  (y << 15)
#define CMATH_TEMPERING_SHIFT_L(y)  (y >> 18)

#define RADIANS_TO_DEGREES(x) ((x) * 180.0f / PI)
#define DEGREES_TO_RADIANS(x) ((x) * PI / 180.0f)


class CRandom
{
private:
	// DATA
	unsigned int		rseed;
	unsigned int		rseed_sp;
	unsigned long mt[CMATH_N]; /* the array for the state vector  */
	int mti; /* mti==N+1 means mt[N] is not initialized */

	// FUNCTIONS
public:
	CRandom();	

	unsigned int	Random(unsigned int n);
	float			Random();
	void			SetRandomSeed(unsigned int n);
	unsigned int	GetRandomSeed();
	void			Randomize();
};


typedef std::vector<Vector2> Poly;

class Math
{
	// DATA
private:
	static const unsigned short angle_to_sin[90];

public:
	static CRandom random;

	// FUNCTIONS
public:
	static int				Cos(short angle, int length);
	static int				Sin(short angle, int length);
	static unsigned int		Sqrt(unsigned int n);
	static void				InterpolateLine(int* x, int* y, int end_x, int end_y, int step_size);
	static unsigned short	GetAngle(int x, int y);
	static bool				PointInPoly(const Vector2& test, const Poly& polygon);
	static bool				PointInPoly(int const x, int const y, int const* const vertex, int const nVertex);
	static RECT				BoundingBox(const POINT& pt1, const POINT& pt2, const POINT& pt3, const POINT& pt4);
	static RECT				BoundingBox(const POINT* p_verts, const unsigned int nVerts);
	static float const		GetDistanceBetween(const POINT& pt1, const POINT& pt2);

	// Used to determine the bounding box for a range of point-like objects.
	// This includes POINTS, Vector2, and VertexUV to name a few.
	// This works on any range which includes all STL containers as well as C style arrays.
	// See BoundingBox(const POINT*, const unsigned int) in cpp for example usage.
	template <typename PointType>
	class BoundingBoxFinder : std::unary_function<PointType, void>
	{
	public:
		void operator()(PointType const& item)
		{
			if(m_oundingBox.invalid())
			{
				RECT initialValue = { item.x, item.y, item.x, item.y };
				m_oundingBox = initialValue;
			}
			else
			{
				m_oundingBox->left = std::min(m_oundingBox->left, item.x);
				m_oundingBox->top = std::min(m_oundingBox->top, item.y);
				m_oundingBox->right = std::max(m_oundingBox->right, item.x);
				m_oundingBox->bottom = std::max(m_oundingBox->bottom, item.y);
			}
		}

		const RECT& BoundingBox() {return *m_oundingBox;}

	private:
		optional<RECT> m_oundingBox;
	};
};

// Lines

#define	DONT_INTERSECT    0
#define	DO_INTERSECT      1
#define COLLINEAR         2

struct LineSegment
{
	Vector2 m_egin, m_end;
	LineSegment(const Vector2& begin, const Vector2& end) {m_egin=begin; m_end=end;}
	LineSegment() {m_egin = m_end = Vector2(0.f, 0.f);}
};

int LinesIntersect(Vector2 one,		/* First line segment */
				   Vector2 two,

				   Vector2 three,   /* Second line segment */
				   Vector2 four,

				   Vector2& result
				   );

bool Intersect(const RECT &rect, const Vector2 &center, double const radius);

float WrapPi(float wrapMe);  // wraps angle so it's between -PI and PI
float Wrap2Pi(float wrapMe);  // wraps angle so it's between 0 and 2PI
float AngleDiff(float lhs, float rhs);
Vector3 GetVectorFromYRotation(float angleRadians);
float GetYRotationFromVector(const Vector3& lookAt);


inline Vector3 CalcVelocity(const Vector3& pos0, const Vector3& pos1, float time)
{
	return (pos1 - pos0) / time;
}

inline Vector3 CalcAcceleration(const Vector3& vel0, const Vector3& vel1, float time)
{
	return (vel1 - vel0) / time;
}

inline void HandleAccel(Vector3& pos, Vector3& vel, Vector3& accel, float time)
{
	vel += accel * time;
	pos += vel * time;
}